/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.basics.bean;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@link ClassLoader} that allows to define new resources dynamically.
 */
public class ConfigurableClassLoader
    extends URLClassLoader
{

  /**
   * the <code>Log</code> variabe.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurableClassLoader.class);

  /**
   * the new resources.
   */
  private transient final Map<String, File> resources = Collections.synchronizedMap(new HashMap<String, File>());



  /**
   * constructor.
   * 
   * @param parent
   *          the parent {@link ClassLoader}
   */
  public ConfigurableClassLoader(ClassLoader parent)
  {
    super(new URL[0], parent);
  }



  /**
   * Adding a new resource to this {@link ClassLoader}.
   * 
   * @param path
   *          the path of the resource
   * @param file
   *          the {@link File} containing the content of the resource
   */
  public void addResource(String path, File file)
  {
    resources.put(path, file);
  }



  /**
   * Finds the resource with the given name. A resource is some data (images,
   * audio, text, etc) that can be accessed by class code in a way that is
   * independent of the location of the code.
   * <p>
   * The name of a resource is a '<tt>/</tt>'-separated path name that
   * identifies the resource.
   * <p>
   * This method will first search the parent class loader for the resource; if
   * the parent is <tt>null</tt> the path of the class loader built-in to the
   * virtual machine is searched. That failing, this method will invoke
   * {@link #findResource(String)} to find the resource.
   * </p>
   * 
   * @param name
   *          The resource name
   * @return A <tt>URL</tt> object for reading the resource, or <tt>null</tt>
   *         if the resource could not be found or the invoker doesn't have
   *         adequate privileges to get the resource.
   * @since 1.1
   * @see java.lang.ClassLoader#getResource(java.lang.String)
   */
  @Override
  public URL getResource(String name)
  {
    URL resource = findConfigurableResource(name);
    if (resource != null) {
      return resource;
    }
    return super.getResource(name);
  }



  /**
   * Finds all the resources with the given name. A resource is some data
   * (images, audio, text, etc) that can be accessed by class code in a way that
   * is independent of the location of the code.
   * <p>
   * The name of a resource is a <tt>/</tt>-separated path name that
   * identifies the resource.
   * <p>
   * The search order is described in the documentation for {@link
   * #getResource(String)}.
   * </p>
   * 
   * @param name
   *          The resource name
   * @return An enumeration of {@link java.net.URL <tt>URL</tt>} objects for the
   *         resource. If no resources could be found, the enumeration will be
   *         empty. Resources that the class loader doesn't have access to will
   *         not be in the enumeration.
   * @throws IOException
   *           If I/O errors occur
   * @see #findResources(String)
   * @since 1.2
   * @see java.lang.ClassLoader#getResources(java.lang.String)
   */
  @Override
  public Enumeration<URL> getResources(String name)
      throws IOException
  {
    URL resource = findConfigurableResource(name);
    if (resource == null) {
      return super.getResources(name);
    }
    @SuppressWarnings("unchecked")
    Enumeration<URL>[] tmp = new Enumeration[2];
    tmp[0] = new EnumerationIterator<URL>(Arrays.asList(resource).iterator());
    tmp[1] = super.getResources(name);
    return new EnumerationAggregator<URL>(tmp);
  }



  /**
   * Returns the search path of URLs for loading classes and resources. This
   * includes the original list of URLs specified to the constructor, along with
   * any URLs subsequently appended by the addURL() method.
   * 
   * @return the search path of URLs for loading classes and resources.
   * @see java.net.URLClassLoader#getURLs()
   */
  @Override
  public URL[] getURLs()
  {
    if (getParent() instanceof URLClassLoader) {
      return ((URLClassLoader) getParent()).getURLs();
    }
    return new URL[0];
  }



  /**
   * find the resource with the given name in the new configured resources.
   * 
   * @param name
   *          the name of the resource
   * @return the new configured resource with the given name, or
   *         <code>null</code> if no such resource exists
   */
  private URL findConfigurableResource(String name)
  {
    File resource = resources.get(name);
    try {
      if (resource != null) {
        return resource.toURL();
      }
    }
    catch (Exception e) {
      LOGGER.error(e.getMessage(), e);
    }
    return null;
  }

}
